use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use predicates::prelude::*;
use std::process::Command;

const COMMAND: &str = env!("CARGO_PKG_NAME");
const FILENAME: &str = "input.txt";

#[test]
fn test_command_main() -> Result<(), Box<dyn std::error::Error>> {
    let temp = assert_fs::TempDir::new().unwrap();
    let file = temp.child(FILENAME);
    let text = r#"int main()
{
   // line comment
   /*
     block comment
   */
   int i = 0x1;
}"#;
    file.write_str(text)?;
    // command test
    let mut command = Command::cargo_bin(COMMAND)?;
    command.current_dir(&temp);
    command.arg(FILENAME);
    command.assert().success().stderr(predicate::str::diff(
        r#"INT int at Line 1.
IDENT main at Line 1.
L_PAREN ( at Line 1.
R_PAREN ) at Line 1.
L_BRACE { at Line 2.
INT int at Line 7.
IDENT i at Line 7.
ASSIGN = at Line 7.
INTEGER_CONST 1 at Line 7.
SEMICOLON ; at Line 7.
R_BRACE } at Line 8.
"#,
    ));
    Ok(())
}

#[test]
fn test_command_error() -> Result<(), Box<dyn std::error::Error>> {
    let temp = assert_fs::TempDir::new().unwrap();
    let file = temp.child(FILENAME);
    let text = r#"int main(){
  int i = 1;
  int j = ~i;
}"#;
    file.write_str(text)?;
    // command test
    let mut command = Command::cargo_bin(COMMAND)?;
    command.current_dir(&temp);
    command.arg(FILENAME);
    command.assert().success().stderr(predicate::str::diff(
        "Error type A at Line 3: Mysterious character \"~\".\n",
    ));
    Ok(())
}

#[test]
fn test_command_func() -> Result<(), Box<dyn std::error::Error>> {
    let temp = assert_fs::TempDir::new().unwrap();
    let file = temp.child(FILENAME);
    let text = r#"int func(int arg) {
    int l;
    l = - - - arg;
    return l;
}

int main() {
    int x, y;
    x = 02;
    y = 0x1;
    x = x - 1 + y;
    if (+-!!!x) {
        x = - - -2;
    }
    else {
        x = 1 + + y;
    }
    func(x);
    return 0;
}"#;
    file.write_str(text)?;
    // command test
    let mut command = Command::cargo_bin(COMMAND)?;
    command.current_dir(&temp);
    command.arg(FILENAME);
    command.assert().success().stderr(predicate::str::diff(
        r#"INT int at Line 1.
IDENT func at Line 1.
L_PAREN ( at Line 1.
INT int at Line 1.
IDENT arg at Line 1.
R_PAREN ) at Line 1.
L_BRACE { at Line 1.
INT int at Line 2.
IDENT l at Line 2.
SEMICOLON ; at Line 2.
IDENT l at Line 3.
ASSIGN = at Line 3.
MINUS - at Line 3.
MINUS - at Line 3.
MINUS - at Line 3.
IDENT arg at Line 3.
SEMICOLON ; at Line 3.
RETURN return at Line 4.
IDENT l at Line 4.
SEMICOLON ; at Line 4.
R_BRACE } at Line 5.
INT int at Line 7.
IDENT main at Line 7.
L_PAREN ( at Line 7.
R_PAREN ) at Line 7.
L_BRACE { at Line 7.
INT int at Line 8.
IDENT x at Line 8.
COMMA , at Line 8.
IDENT y at Line 8.
SEMICOLON ; at Line 8.
IDENT x at Line 9.
ASSIGN = at Line 9.
INTEGER_CONST 2 at Line 9.
SEMICOLON ; at Line 9.
IDENT y at Line 10.
ASSIGN = at Line 10.
INTEGER_CONST 1 at Line 10.
SEMICOLON ; at Line 10.
IDENT x at Line 11.
ASSIGN = at Line 11.
IDENT x at Line 11.
MINUS - at Line 11.
INTEGER_CONST 1 at Line 11.
PLUS + at Line 11.
IDENT y at Line 11.
SEMICOLON ; at Line 11.
IF if at Line 12.
L_PAREN ( at Line 12.
PLUS + at Line 12.
MINUS - at Line 12.
NOT ! at Line 12.
NOT ! at Line 12.
NOT ! at Line 12.
IDENT x at Line 12.
R_PAREN ) at Line 12.
L_BRACE { at Line 12.
IDENT x at Line 13.
ASSIGN = at Line 13.
MINUS - at Line 13.
MINUS - at Line 13.
MINUS - at Line 13.
INTEGER_CONST 2 at Line 13.
SEMICOLON ; at Line 13.
R_BRACE } at Line 14.
ELSE else at Line 15.
L_BRACE { at Line 15.
IDENT x at Line 16.
ASSIGN = at Line 16.
INTEGER_CONST 1 at Line 16.
PLUS + at Line 16.
PLUS + at Line 16.
IDENT y at Line 16.
SEMICOLON ; at Line 16.
R_BRACE } at Line 17.
IDENT func at Line 18.
L_PAREN ( at Line 18.
IDENT x at Line 18.
R_PAREN ) at Line 18.
SEMICOLON ; at Line 18.
RETURN return at Line 19.
INTEGER_CONST 0 at Line 19.
SEMICOLON ; at Line 19.
R_BRACE } at Line 20.
"#,
    ));
    Ok(())
}


#[test]
fn test_command_array() -> Result<(), Box<dyn std::error::Error>> {
    let temp = assert_fs::TempDir::new().unwrap();
    let file = temp.child(FILENAME);
    let text = r#"int array()
{
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    int a1 = 0, a2 = 3, a3 = 5, a4 = 7, a5 = 9, a6 = 1, a7 = 2, a8 = 4,
        a9 = 6;

    return arr[a1] + arr[a2] + arr[a3] + arr[a4] + arr[a7] + arr[a8];
}

int main()
{
    int q = 1, r = 2, s = 04, t = 0x7, u = 0xA, v = 0xb, w = 0xcD, x = 077;

    int sum1 = q + r + s + t + u + v + w + x;

    int sum2 = array();

    int sum3 = sum1 + sum2;

    return 0;
}"#;
    file.write_str(text)?;
    // command test
    let mut command = Command::cargo_bin(COMMAND)?;
    command.current_dir(&temp);
    command.arg(FILENAME);
    command.assert().success().stderr(predicate::str::diff(
        r#"INT int at Line 1.
IDENT array at Line 1.
L_PAREN ( at Line 1.
R_PAREN ) at Line 1.
L_BRACE { at Line 2.
INT int at Line 3.
IDENT arr at Line 3.
L_BRACKT [ at Line 3.
INTEGER_CONST 10 at Line 3.
R_BRACKT ] at Line 3.
ASSIGN = at Line 3.
L_BRACE { at Line 3.
INTEGER_CONST 0 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 1 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 2 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 3 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 4 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 5 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 6 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 7 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 8 at Line 3.
COMMA , at Line 3.
INTEGER_CONST 9 at Line 3.
R_BRACE } at Line 3.
SEMICOLON ; at Line 3.
INT int at Line 5.
IDENT a1 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 0 at Line 5.
COMMA , at Line 5.
IDENT a2 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 3 at Line 5.
COMMA , at Line 5.
IDENT a3 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 5 at Line 5.
COMMA , at Line 5.
IDENT a4 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 7 at Line 5.
COMMA , at Line 5.
IDENT a5 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 9 at Line 5.
COMMA , at Line 5.
IDENT a6 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 1 at Line 5.
COMMA , at Line 5.
IDENT a7 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 2 at Line 5.
COMMA , at Line 5.
IDENT a8 at Line 5.
ASSIGN = at Line 5.
INTEGER_CONST 4 at Line 5.
COMMA , at Line 5.
IDENT a9 at Line 6.
ASSIGN = at Line 6.
INTEGER_CONST 6 at Line 6.
SEMICOLON ; at Line 6.
RETURN return at Line 8.
IDENT arr at Line 8.
L_BRACKT [ at Line 8.
IDENT a1 at Line 8.
R_BRACKT ] at Line 8.
PLUS + at Line 8.
IDENT arr at Line 8.
L_BRACKT [ at Line 8.
IDENT a2 at Line 8.
R_BRACKT ] at Line 8.
PLUS + at Line 8.
IDENT arr at Line 8.
L_BRACKT [ at Line 8.
IDENT a3 at Line 8.
R_BRACKT ] at Line 8.
PLUS + at Line 8.
IDENT arr at Line 8.
L_BRACKT [ at Line 8.
IDENT a4 at Line 8.
R_BRACKT ] at Line 8.
PLUS + at Line 8.
IDENT arr at Line 8.
L_BRACKT [ at Line 8.
IDENT a7 at Line 8.
R_BRACKT ] at Line 8.
PLUS + at Line 8.
IDENT arr at Line 8.
L_BRACKT [ at Line 8.
IDENT a8 at Line 8.
R_BRACKT ] at Line 8.
SEMICOLON ; at Line 8.
R_BRACE } at Line 9.
INT int at Line 11.
IDENT main at Line 11.
L_PAREN ( at Line 11.
R_PAREN ) at Line 11.
L_BRACE { at Line 12.
INT int at Line 13.
IDENT q at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 1 at Line 13.
COMMA , at Line 13.
IDENT r at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 2 at Line 13.
COMMA , at Line 13.
IDENT s at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 4 at Line 13.
COMMA , at Line 13.
IDENT t at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 7 at Line 13.
COMMA , at Line 13.
IDENT u at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 10 at Line 13.
COMMA , at Line 13.
IDENT v at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 11 at Line 13.
COMMA , at Line 13.
IDENT w at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 205 at Line 13.
COMMA , at Line 13.
IDENT x at Line 13.
ASSIGN = at Line 13.
INTEGER_CONST 63 at Line 13.
SEMICOLON ; at Line 13.
INT int at Line 15.
IDENT sum1 at Line 15.
ASSIGN = at Line 15.
IDENT q at Line 15.
PLUS + at Line 15.
IDENT r at Line 15.
PLUS + at Line 15.
IDENT s at Line 15.
PLUS + at Line 15.
IDENT t at Line 15.
PLUS + at Line 15.
IDENT u at Line 15.
PLUS + at Line 15.
IDENT v at Line 15.
PLUS + at Line 15.
IDENT w at Line 15.
PLUS + at Line 15.
IDENT x at Line 15.
SEMICOLON ; at Line 15.
INT int at Line 17.
IDENT sum2 at Line 17.
ASSIGN = at Line 17.
IDENT array at Line 17.
L_PAREN ( at Line 17.
R_PAREN ) at Line 17.
SEMICOLON ; at Line 17.
INT int at Line 19.
IDENT sum3 at Line 19.
ASSIGN = at Line 19.
IDENT sum1 at Line 19.
PLUS + at Line 19.
IDENT sum2 at Line 19.
SEMICOLON ; at Line 19.
RETURN return at Line 21.
INTEGER_CONST 0 at Line 21.
SEMICOLON ; at Line 21.
R_BRACE } at Line 22.
"#,
    ));
    Ok(())
}


#[test]
fn test_command_equal() -> Result<(), Box<dyn std::error::Error>> {
    let temp = assert_fs::TempDir::new().unwrap();
    let file = temp.child(FILENAME);
    let text = r#"int main(){
  if (i == j ){
    return i * 2;
  } else {
    return i + j;
  }
}"#;
    file.write_str(text)?;
    // command test
    let mut command = Command::cargo_bin(COMMAND)?;
    command.current_dir(&temp);
    command.arg(FILENAME);
    command.assert().success().stderr(predicate::str::diff(
        r#"INT int at Line 1.
IDENT main at Line 1.
L_PAREN ( at Line 1.
R_PAREN ) at Line 1.
L_BRACE { at Line 1.
IF if at Line 2.
L_PAREN ( at Line 2.
IDENT i at Line 2.
EQ == at Line 2.
IDENT j at Line 2.
R_PAREN ) at Line 2.
L_BRACE { at Line 2.
RETURN return at Line 3.
IDENT i at Line 3.
MULT * at Line 3.
INTEGER_CONST 2 at Line 3.
SEMICOLON ; at Line 3.
R_BRACE } at Line 4.
ELSE else at Line 4.
L_BRACE { at Line 4.
RETURN return at Line 5.
IDENT i at Line 5.
PLUS + at Line 5.
IDENT j at Line 5.
SEMICOLON ; at Line 5.
R_BRACE } at Line 6.
R_BRACE } at Line 7.
"#,
    ));
    Ok(())
}